﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RESTFinder.Utils.Serialization;

namespace RESTFinder.Utils.Client
{
    public class RESTWebClient : WebClient
    {
        private WebRequest request = null;
        private XmlPipelineSerializer xmlPipelineSerializer = new XmlPipelineSerializer();
        private JsonPipelineSerializer jsonPipelineSerializer = new JsonPipelineSerializer();

        protected override WebRequest GetWebRequest(Uri address)
        {
            request = base.GetWebRequest(address);

            if (request is HttpWebRequest)
            {
                ((HttpWebRequest)this.request).AllowAutoRedirect = false;
            }

            return this.request;
        }

        public  Task<RESTResponse<T>> Get<T>(string url, SerializationToUse serializationToUse)
        {
            var t = Task.Factory.StartNew( () =>
            {
                var task = Task.Factory.StartNew(() => DownloadString(url));

                string response = task.Result;
                return CreateResponse<T>(response, serializationToUse);
            });
            return t.Result;
        }

        public  Task<RESTResponse<T>> Post<T>(string url, T item, SerializationToUse serializationToUse)
        {
            return  Task.Factory.StartNew( () =>
            {
                var t = UploadDataForMethod(url, "POST", item, serializationToUse);
                t.Wait();
                byte[] responsebytes = t.Result;
                string responsebody = string.Empty;
                if (serializationToUse == SerializationToUse.Xml)
                {
                    responsebody = Encoding.UTF8.GetString(responsebytes);
                }
                if (serializationToUse == SerializationToUse.Json)
                {
                    responsebody = Encoding.UTF8.GetString(responsebytes);
                }
                var tt =  CreateResponse<T>(responsebody, serializationToUse);
                return tt.Result;
            });
        }

        public  Task<HttpStatusCode> Delete(string url)
        {
            return  Task.Factory.StartNew( () =>
            {
                var request = WebRequest.Create(url);
                request.Method = "DELETE";
                var response = request.GetResponse();
                return ((HttpWebResponse) response).StatusCode;
            });
        }

        public  Task<HttpStatusCode> Put<T>(string url, T item, SerializationToUse serializationToUse)
        {
            var task = Task.Factory.StartNew(() =>
            {
                UploadDataForMethod(url, "PUT", item, serializationToUse);
                return StatusCode();
            });
            task.Wait();

            return task.Result;
        }

        private Task<byte[]> UploadDataForMethod<T>(string url, string httpMethod, T item, SerializationToUse serializationToUse)
        {
            var task = Task.Factory.StartNew(() =>
          {
              if (serializationToUse == SerializationToUse.Xml)
              {
                  Headers.Add("Content-Type", "application/xml");
                  var t = xmlPipelineSerializer.SerializeAsBytes(item);
                  t.Wait();
                  var serialized = t.Result;
                  return Task.Factory.StartNew(() => UploadData(url, httpMethod, serialized));
              }
              if (serializationToUse == SerializationToUse.Json)
              {
                  Headers.Add("Content-Type", "application/json");
                  var t = jsonPipelineSerializer.SerializeAsBytes(item);
                  t.Wait();
                  var serialized = t.Result;
                  return Task.Factory.StartNew(() => UploadData(url, httpMethod, serialized));
              }
              throw new InvalidOperationException("You need to specify either Xml or Json serialization");
          });
            throw new InvalidOperationException("You need to specify either Xml or Json serialization");
        }


        private Task<HttpStatusCode> StatusCode()
        {
            return Task.Factory.StartNew(() =>
           {
               if (this.request == null)
               {
                   throw (new InvalidOperationException(
                       "Unable to retrieve the status code, maybe you haven't made a request yet."));
               }

               HttpWebResponse response = base.GetWebResponse(this.request) as HttpWebResponse;

               if (response != null)
               {
                   return response.StatusCode;
               }
               throw (new InvalidOperationException(
                   "Unable to retrieve the status code, maybe you haven't made a request yet."));
           });
        }

        private  Task<RESTResponse<T>> CreateResponse<T>(string response, SerializationToUse serializationToUse)
        {
            return  Task.Factory.StartNew( () =>
            {
                if (serializationToUse == SerializationToUse.Xml)
                {
                    var t = xmlPipelineSerializer.Deserialize<T>(response);
                    t.Wait();

                    var tt = StatusCode();
                    tt.Wait();
                    return new RESTResponse<T>()
                    {
                        Content = t.Result,
                        StatusCode = tt.Result,
                    };
                }
                if (serializationToUse == SerializationToUse.Json)
                {
                    var t = jsonPipelineSerializer.Deserialize<T>(response);
                    t.Wait();
                    var tt = StatusCode();
                    tt.Wait();
                    return new RESTResponse<T>()
                    {
                        Content = t.Result ,
                        StatusCode = tt.Result
                    };
                }
                throw new InvalidOperationException("You need to specify either Xml or Json serialization");
            });
        }

    }
}
